//
//  AppRun.h
//  面试题
//
//  Created by Toj on 8/11/21.
//

https://juejin.cn/post/6982586012111208485
https://www.jianshu.com/p/c699315611d7
https://juejin.cn/post/7110502184894070798
https://juejin.cn/post/6983320661590540325
https://juejin.cn/post/7006927993742295053
https://nshipster.com/inter-process-communication/
https://segmentfault.com/a/1190000002400329


#pragma mark - 1. App启动过程

1.1. 加载环境变量
1.1.1 解析Info.plist
1.1.1.1 加载相关信息，例如如闪屏
1.1.1.2 沙箱建立、权限检查
1.2 Mach-O加载
1.2.1 如果是胖二进制文件，寻找合适当前CPU类别的部分
1.2.2 加载所有依赖的Mach-O文件（递归调用Mach-O加载的方法）
1.2.3 定位内部、外部指针引用，例如字符串、函数等
1.2.4 执行声明为__attribute__((constructor))的C函数
1.2.5 加载类扩展（Category）中的方法
1.2.6 C++静态对象加载、调用ObjC的 +load 函数
1.3 程序执行
1.3.1 调用main()
1.3.2 调用UIApplicationMain()
1.3.3 调用applicationWillFinishLaunching


#pragma mark - App打包 流程
App打包 流程 请看 -> App编译过程.png
后会存到硬盘中, 当点击App第一次启动后,dyld

#pragma mark - 静态库, 动态库
静态库: 静态库链接时会被copy可执行文件中, 多次用到就会多次copy, 浪费资源空间
动态库: 自定义的 app 内共享, 系统库 全局共享

#pragma mark - App启动过程
App启动过程 -> App 启动过程.png

#pragma mark 程序的入口 -> 动态链接器: dyld
dyld源码:
https://opensource.apple.com/tarballs/dyld/
dyld_cache_extract提取:
https://github.com/macmade/dyld_cache_extract

#pragma mark dyld: 1._dyld_start 从汇编开始
文件位置: dyld-852/src/dyldStartup.s -> 134行 {
__dyld_start:
    popq    %rdi        # param1 = mh of app
    pushq    $0        # push a zero for debugger end of frames marker
    movq    %rsp,%rbp    # pointer to base of kernel frame
    andq    $-16,%rsp       # force SSE alignment
    subq    $16,%rsp    # room for local variables

    # call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
    movl    8(%rbp),%esi    # param2 = argc into %esi
    leaq    16(%rbp),%rdx    # param3 = &argv[0] into %rdx
    leaq    ___dso_handle(%rip),%rcx # param4 = dyldsMachHeader into %rcx
    leaq    -8(%rbp),%r8    # param5 = &glue into %r8
    call    __ZN13dyldbootstrap5startEPKN5dyld311MachOLoadedEiPPKcS3_Pm
    movq    -8(%rbp),%rdi
    cmpq    $0,%rdi
    jne    Lnew

        # clean up stack and jump to "start" in main executable
    movq    %rbp,%rsp    # restore the unaligned stack pointer
    addq    $8,%rsp     # remove the mh argument, and debugger end frame marker
    movq    $0,%rbp        # restore ebp back to zero
    jmp    *%rax        # jump to the entry point

    # LC_MAIN case, set up stack for call to main()
}

首先程序执行后会执行一段汇编程序(看不懂), 这里就是程序启动的入口
根据提示可以看出 汇编代码调用了一段C++函数
// call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)

#pragma mark dyld: 从C++start开始, dyldbootstrap::start
文件位置: dyld-852/src/dyldInitialization.cpp -> 92行

uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
                const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{

    // Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536>
    // 发出信号dyld 已经启动
    dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);

    // if kernel had to slide dyld, we need to fix up load sensitive locations
    // we have to do this before using any global variables
    // 如果内核需要变动, 改变dyld的位置(从新定位dyld)
    rebaseDyld(dyldsMachHeader);

    // kernel sets up env pointer to be just past end of agv array
    const char** envp = &argv[argc+1];
    
    // kernel sets up apple pointer to be just past end of envp array
    const char** apple = envp;
    while(*apple != NULL) { ++apple; }
    ++apple;

    // set up random value for stack canary
    __guard_setup(apple);

#if DYLD_INITIALIZER_SUPPORT
    // run all C++ initializers inside dyld
    // 运行所有C++初始化器
    runDyldInitializers(argc, argv, envp, apple);
#endif

    _subsystem_init(apple);

    // now that we are done bootstrapping dyld, call dyld's main
    // 调用dyld的main方法
    uintptr_t appsSlide = appsMachHeader->getSlide();
    return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}

// 最后 return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);

#pragma mark dyld: 真正的开始 dyld::_main
函数文件: dyld_main.h
文件位置: SourceCode/dyld-852/src/dyld2.cpp -> 6453行


void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
    dyld::registerObjCNotifiers(mapped, init, unmapped);
}

void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    // record functions to call
    sNotifyObjCMapped    = mapped;
    sNotifyObjCInit        = init;
    sNotifyObjCUnmapped = unmapped;

    // call 'mapped' function with all images mapped so far
    try {
        notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
    }
    catch (const char* msg) {
        // ignore request to abort during registration
    }

    // <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
    for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
        ImageLoader* image = *it;
        if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
            dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
            (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        }
    }
}

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init(); // 分类加载?
// 加载类
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
